package us.styx.net;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import us.styx.Constants;

public class NetworkServer {
    /*
     * The initial implementation of the 'ServerSocketChannel'. This
     * non-blocking instance will be registered with our 'Selector' to accept
     * incoming connections and read their viable data.
     */
    private static ServerSocketChannel serverSocketChannel;
    /*
     * The address the application will be listening upon. By default the
     * numerical port will be set as '43594'.
     */
    private static InetSocketAddress applicationAddress;
    /*
     * Holds the connections which are now pending to be accepted.
     */
    public static Queue<Socket> connections = new LinkedList<Socket>();
    /*
     * Holds the connections which are now pending decoding.
     */
    public static Map<SelectionKey, Client> acceptedConnections = new HashMap<SelectionKey, Client>();
    /*
     * Our thread safe 'Selector'. Broken down into three parts it will be
     * utilized for reading, writing, and accepting incoming connections. Even
     * though the 'Selector' itself is thread safe, it's keys are not.
     */
    public static Selector selector;
    /*
     * Initializes the entire networking system behind the application. The
     * server's networking as well as 'Selector' are both constructed here.
     */
    public static void initializeNetworkServer() {
	try {
	    setApplicationAddress(new InetSocketAddress(Constants.SERVER_ADDRESS, Constants.SERVER_PORT));
	    setServerSocketChannel(ServerSocketChannel.open());
	    getServerSocketChannel().socket().bind(getApplicationAddress());
	    getServerSocketChannel().configureBlocking(false);
	    selector = SelectorProvider.provider().openSelector();
	    getServerSocketChannel().register(selector, SelectionKey.OP_ACCEPT);
	    NetworkSelector netSelector = new NetworkSelector();
	    ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
	    scheduler.scheduleAtFixedRate(netSelector, 0, Constants.RELAY, TimeUnit.MILLISECONDS);
	    System.out.println("Styx has bound to the address: [" + getServerSocketChannel().socket() + "].");
	} catch (IOException exception) {
	    exception.printStackTrace();
	}
    }

    public static ServerSocketChannel getServerSocketChannel() {
	return serverSocketChannel;
    }

    public static void setServerSocketChannel(ServerSocketChannel serverSocket) {
	NetworkServer.serverSocketChannel = serverSocket;
    }

    public static InetSocketAddress getApplicationAddress() {
	return applicationAddress;
    }

    public static void setApplicationAddress(InetSocketAddress applicationAddress) {
	NetworkServer.applicationAddress = applicationAddress;
    }
}